package com.nf.db;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.time.LocalDate;
import java.sql.Date;
import java.util.*;

public class DefaultRowProcessor implements RowProcessor {

    //下面这种写法对其循环迭代时有线程安全的问题，apache dbutils在1.8之后的版本进行了修改
    // private static final ServiceLoader<PropertyHandler> propertyHandlers = ServiceLoader.load(PropertyHandler.class);
    private static final List<PropertyHandler> PROPERTY_HANDLERS = new ArrayList<>();

    static {
        ServiceLoader.load(PropertyHandler.class).forEach(PROPERTY_HANDLERS::add);
    }

    @Override
    public Object[] toArray(ResultSet rs) throws SQLException {
        ResultSetMetaData metaData = rs.getMetaData();
        int columnCount = metaData.getColumnCount();
        Object[] result = new Object[columnCount];
        //[1,zhangsan,5000] columnCount =3  select id,name,salary from
        for (int i = 0; i < columnCount; i++) {
            result[i] = rs.getObject(i + 1);
        }
        return result;
    }

    @Override
    public Map<String, Object> toMap(ResultSet rs) throws SQLException {
        Map<String, Object> result = new HashMap<>();

        ResultSetMetaData metaData = rs.getMetaData();
        int columnCount = metaData.getColumnCount();
        for (int i = 0; i < columnCount; i++) {
            String key = metaData.getColumnName(i + 1);
            Object value = rs.getObject(i + 1);
            result.put(key, value);
        }

        return result;
    }

    @Override
    public <T> T toBean(ResultSet rs, Class<?> beanClz) throws SQLException {
        //要求实体类这个bean必须有默认构造函数
        Object bean = newInstance(beanClz);
        fillBean(rs, bean);
        return (T) bean;
    }

    @Override
    public <T> T toScalar(ResultSet rs, String columnName) throws SQLException {
        return (T) rs.getObject(columnName);
    }


    private Object newInstance(Class<?> beanClz) {
        Object bean;
        try {
            bean = beanClz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            //这种catch写法是jdk的新特性，等价于分别写2个catch
            throw new RuntimeException(e);
        }
        return bean;
    }

    private void fillBean(ResultSet rs, Object bean) throws SQLException {

        ResultSetMetaData metaData = rs.getMetaData();
        int columnCount = metaData.getColumnCount();
        for (int i = 0; i < columnCount; i++) {
            //得到结果集的各个列的名字,最好用别名.如果sql语句中没有别名getColumnLabel效果等价于getColumnName
            String columnName = metaData.getColumnLabel(i + 1);
            //依据sql的列名(支持别名)找到实体类对应的字段
            Field field = getField(bean, columnName);
            //找不到sql语句对应的字段直接不对字段进行赋值处理就可以了
            if (field == null) {
                continue;
            }
            //当前这个列对应的值
            Object value = rs.getObject(i + 1);
            //利用spi技术对值进行一些处理
            applyPropertyHandler(value, field.getType());
            //把处理之后的value赋值给字段
            setFieldValue(bean, field, value);
        }
    }

    private Field getField(Object bean, String columnName) {
        Field field = null;
        try {
            field = bean.getClass().getDeclaredField(columnName);
        } catch (NoSuchFieldException e) {
            // System.out.println("找不到字段，什么都不用干，相当于跳过了这个字段的后续赋值的处理");
        }
        return field;
    }

    private void applyPropertyHandler(Object value, Class<?> propertyClz) {
       /*
        //数据库日期类型的列的数据是用java.sql.Date类型表示的,它是java.util.Date的子类型
        //如果你的实体类是新jdk推荐用的日期类型，比如LocalDate，LocalTime等等
        // 他们之间是不能直接赋值的
        //java.slq.Date d1 = ...
        //java.time.LocalDate d2 = d1 //报错
        //要想不报错，就转换一下，比如下面代码即可解决
        //d2 = d1.toLocalDate();
            if (value instanceof Date && field.getType() == LocalDate.class) {
                value = ((Date) value).toLocalDate();
            }
*/
        for (PropertyHandler handler : PROPERTY_HANDLERS) {
            if (handler.match(propertyClz, value)) {
                value = handler.handle(propertyClz, value);
            }
        }
    }

    private void setFieldValue(Object bean, Field field, Object value) {
        try {
            field.setAccessible(true);
            field.set(bean, value);
            field.setAccessible(false);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}
